package com.lottery.game.runner.v2;

import com.alibaba.fastjson.JSON;
import com.lottery.game.common.constants.GameConstants;
import com.lottery.game.common.utils.DateUtils;
import com.lottery.game.common.utils.SpringApplicationUtils;
import com.lottery.game.common.utils.StringUtil;
import com.lottery.game.core.model.ResultEntity;
import com.lottery.game.core.model.dto.game.GameTypePlayingRelationTO;
import com.lottery.game.core.modules.entity.game.GameRoom;
import com.lottery.game.core.modules.entity.user.GameIssue;
import com.lottery.game.core.modules.service.app.BetService;
import com.lottery.game.core.modules.service.app.WebsocketMessageService;
import com.lottery.game.core.modules.service.game.GameTypePlayingRelationService;
import com.lottery.game.core.utils.BetUtils;
import com.lottery.game.core.utils.IssueGeneratorUtils;
import com.lottery.game.core.utils.ResultCode;
import com.lottery.game.runner.RoomManager;
import com.lottery.game.runner.v2.trigger.RoomOfflineTrigger;
import com.lottery.game.runner.v2.utils.DataUtil;
import com.lottery.game.runner.v2.utils.RoomUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 替代 com.runshang.apps.cloudgame.runner.RoomManager
 * @author Jake
 *
 */
@Slf4j
public class RoomCoreExecutor extends Thread {

	private volatile boolean allow = true;

	private RoomCoreParam roomCoreParam;
	private RoomUtils roomUtils;
	private BetService betService;
	private RoomOfflineTrigger offlineTrigger;
	private RobotSpeakExecutor robotSpeakExecutor;
	private GamblingClosuresExecutor gamblingClosuresExecutor;
	private WebsocketMessageService websocketMessageService;
	private String threadName;
	@Autowired
	private GameThreadRunner gameThreadRunner;
	@Autowired
	private GameTypePlayingRelationService resourceClient;

	/**
	 * Description: disable flag
	 */
	public void disallow() {
		allow = false;
		interrupt();
	}

	public void enallow() {
		allow = true;
	}

	public RoomCoreExecutor(
			RoomCoreParam roomCoreParam,
			RoomUtils roomUtils,
			BetService betService,
			RoomOfflineTrigger offlineTrigger,
			RobotSpeakExecutor robotSpeakExecutor,
			GamblingClosuresExecutor gamblingClosuresExecutor,
			WebsocketMessageService websocketMessageService
			) {
		this.roomCoreParam = roomCoreParam;
		this.roomUtils = roomUtils;
		this.betService = betService;
		this.offlineTrigger = offlineTrigger;
		this.robotSpeakExecutor = robotSpeakExecutor;
		this.gamblingClosuresExecutor = gamblingClosuresExecutor;
		this.websocketMessageService = websocketMessageService;
		this.threadName = GameConstants.THREAD_PREFIX + roomCoreParam.getRoomType();
	}

	public String getThreadName() {
		return this.threadName;
	}

	@Override
	public void run() {
		if (roomCoreParam == null) {
			log.error("游戏线程启动异常roomCoreParam参数为空");
			return;
		}

		// 根据roomType查询正在运行的房间
		GameRoom gameRoom = betService.queryDownTimeByType(roomCoreParam.getRoomType());
		log.info("启动开奖线程，开始初始化玩法详情");
		// 初始化玩法详情
		RoomDetail roomDetail = this.initRoomDetail(roomCoreParam);
		//log.info("玩法详情【{}】", JSON.toJSONString(roomDetail));
		//Date lastExpectTime = roomDetail.getOpenTime();
		boolean retry = false;
		while (allow&&!Thread.currentThread().isInterrupted()) {
			try {
			    lotteryOpenRunner(roomDetail, gameRoom, roomDetail.getOpenTime(), retry);
				Thread.sleep(1);
				retry = false;
			} catch (InterruptedException e) {
				return;
			} catch (Exception e) {
				retry = true;
				log.error(e.getMessage(), e);
			}
		}
	}

	/**
	 * 循环执行游戏
	 * @param roomDetail
	 * @param gameRoom
	 */
	private void lotteryOpenRunner(RoomDetail roomDetail, GameRoom gameRoom, Date lastExpectTime, boolean retry) {

			log.info("彩种类型【{}】开奖程序新一轮循环开始时间【{}】，上期期号【{}】", roomDetail.getLotteryName(), DateUtils.getNowDateString(), roomDetail.getExpect());
			// 没有维护时才检测，在维护交给下面else里检测
			if (!roomDetail.isDown()) {
				// 初始检测是否维护 不是维护时间则继续运行，否则进入睡眠等待
				roomDetail = roomUtils.isDownTime(gameRoom, roomDetail);
			}

			// 没有运行，在维护，且维护时间大于0
			if (roomDetail.isDown() && roomDetail.getDownTime() > 0) {
				offlineTrigger.apply(roomDetail, roomCoreParam.getGameRoom());
				roomDetail = roomUtils.isDownTime(gameRoom, roomDetail);
			}

			// 如果没有在维护则运行状态。
			if (!roomDetail.isDown()) {
				String tmpExpect = roomDetail.getExpect();
				//如果生成期次异常
				if (!retry) {
					roomDetail.setExpect(BetUtils.getNextExpect(tmpExpect));
				}
				log.info("游戏类型【{}】正在进行【{}】期，准备获取开奖时间", roomDetail.getBetType(), roomDetail.getExpect());

				// 获取下一期 下一期时间
				try {
				    Date tempOpenTime = roomDetail.getOpenTime();
				    if(!roomDetail.isHandLottery()) {
						roomDetail = roomUtils.getExpectTime(roomDetail);
					}
					// 如果获取下一期号失败，则使用缓存开奖时间
					if (roomDetail.isApiDown()) {
						Date thisExpectTime = BetUtils.addSeconds(lastExpectTime,roomDetail.getGameTime());
						roomDetail.setOpenTime(thisExpectTime);
					}
				} catch (Exception e) {
					// 如果获取下一期号失败，则使用缓存开奖时间
					log.error("游戏类型【{}】，获取【{}】期开奖时间，API已经异常,使用原有时间【{}】加上游戏时间【{}】", roomDetail.getLotteryName(), roomDetail.getExpect(),
								DateUtils.getDateString(roomDetail.getOpenTime()), roomDetail.getGameTime() + roomDetail.getDuration());
					//如果获取下一期号失败，则使用缓存开奖时间
					log.error("游戏类型【{}】，获取【{}】期开奖时间，API已经异常,使用原有时间【{}】加上游戏时间【{}】", roomDetail.getLotteryName(), roomDetail.getExpect(),BetUtils.addSeconds(lastExpectTime,roomDetail.getGameTime()));
					Date thisExpectTime = BetUtils.addSeconds(lastExpectTime,roomDetail.getGameTime());
					roomDetail.setOpenTime(thisExpectTime);
				}

				//再次判断开奖时间是否在维护时间内，如果在维护时间内则不开奖

				log.info("彩种类型【{}】期号【{}】，预计开奖时间【{}】", roomDetail.getLotteryName(), roomDetail.getExpect(), DateUtils.getDateString(roomDetail.getOpenTime()));
				// 获取正在运行房间
				List<GameRoom> roomList = betService.queryRoomList(gameRoom);

				log.info("房间类型【{}】，房间数量【{}】", gameRoom.getRoomLotteryTicketType(), roomList.size());

				//如果游戏房间不为空
				if (!CollectionUtils.isEmpty(roomList)) {
					// 生成推送游戏期号
					String currentIssue = IssueGeneratorUtils.generator(gameRoom.getRoomLotteryTicketType());

					// 插入数据库时间
					long now = System.currentTimeMillis();
					long tmpSleepTime = roomDetail.getOpenTime().getTime() - now;
                    int sleepTempTime = (int) tmpSleepTime/1000;
					log.info("休息【{}】秒后准备开奖, 预计开奖时间【{}】, 现在时间【{}】", tmpSleepTime / 1000, DateUtils.getDateString(new Date(roomDetail.getOpenTime().getTime())),
							DateUtils.getDateString(new Date(now)));

					if (tmpSleepTime < 0) {
						// 如果时间小于0，利用continue跳转到下一期
						log.info("预计开奖时间【{}】，当前时间【{}】，如果时间小于0，利用continue跳转到下一期", roomDetail.getOpenTime(), DateUtils.getNowDateString());
					} else {
						GameIssue gameIssue = new GameIssue();
						gameIssue.setIssueApi(roomDetail.getExpect());
						gameIssue.setStatus(GameConstants.GameIssueStatus_Running);
						List<GameIssue> list = betService.queryGameIssue(gameIssue);

						log.info("获得游戏期次数量【{}】,获得数据为【{}】", list.size(), JSON.toJSONString(list));

						if (list.size() <= 0) {
							betService.addBatchGameIssue(roomList, roomDetail.getRelationTO().getGameTypePlayingTicket(), currentIssue, roomDetail.getExpect(), roomDetail.getOpenTime());
						}

						// 推送已生成期号的消息
						this.sendExpectInitMessage(roomDetail.getExpect(), roomList);
					}

					try {
						//TODO 需要修改
						tmpSleepTime = tmpSleepTime - (roomDetail.getGameTime() * 1000);
						log.info("游戏类型：【{}】期号【{}】,等待用户下注，【{}】秒后将封盘", roomDetail.getBetType(), roomDetail.getExpect(), (tmpSleepTime / 1000));
						if (tmpSleepTime > 0) {
							/**
							 * 机器人发言
							 */
							for (GameRoom room : roomList) {
								robotSpeakExecutor.execute(room.getRoomnumber(), now, tmpSleepTime, roomDetail.getExpect());
							}
							TimeUnit.MILLISECONDS.sleep(tmpSleepTime);
						} else {
							log.info("当前已过开奖时间, 封盘后立即开奖");
						}
					} catch (InterruptedException e) {
						log.error(e.getMessage(), e);
						return;
					}
					//发送封盘消息
					this.sendEntertainedMessage(roomDetail.getExpect(), roomList);
					betService.updateGameIssueClose(GameConstants.GameIssueStatus_Close, currentIssue);
					try {
					    int sleepTime = roomDetail.getGameTime();
					    log.info("游戏类型：【{}】，已封盘，等待开奖，【{}】秒后生成下一期, 【{}】 秒后开奖",
								roomDetail.getBetType(), sleepTime, sleepTime);
						TimeUnit.SECONDS.sleep(sleepTime);
					} catch (InterruptedException e) {
						log.error(e.getMessage(), e);
					}
					//开奖
					this.lotteryOpen(roomDetail.getExpect(), roomDetail.getRelationTO().getGameTypePlayingTicket(), currentIssue, roomList);

				} else {
					while (roomList == null || roomList.size() == 0) {
						try {
							log.info("游戏类型【{}】,没有游戏中的房间，进行60秒等待后重新获取数据。。。", roomDetail.getLotteryName());
							TimeUnit.SECONDS.sleep(60);
						} catch (InterruptedException e) {
							log.error(e.getMessage(), e);
						}
						roomList = betService.queryRoomList(gameRoom);
					}
					roomDetail = getInitExpect(roomDetail);
				}
			} else {
				roomDetail = roomUtils.isDownTime(gameRoom, roomDetail);
				if(log.isDebugEnabled()) {
					log.debug("游戏类型【{}】，检查维护时间是否已过【{}】", roomDetail.getLotteryName(), roomDetail.isDown());
				}
				// 如果维护时间已经过了，
				// 还要检查一遍，api获取的期号是否和维护前相同，如果相同则表示api还没恢复可能是维护延时

				if (!roomDetail.isDown()) {
					// 尝试获取初始期号
					RoomDetail tmpBean = getInitExpect(roomDetail);

					if(log.isDebugEnabled()) {
						log.debug("游戏类型【{}】，维护时间已过【{}】，额外检查期号是否正常,缓存期号【{}】,Api最新期号【{}】",
								roomDetail.getLotteryName(), roomDetail.isDown(), roomDetail.getExpect(), tmpBean.getExpect());
					}
					// 如果相同期号,或比较小的旗号，表示维护还未结束
					if (tmpBean.getExpect().compareTo(roomDetail.getExpect()) > 0) {
						roomDetail = getInitExpect(roomDetail);
					} else {
						roomDetail.setDown(true);
						log.info("游戏类型：【{}】，维护尚未结束。。。10秒后重新验证。", roomDetail.getBetType());
					}
				}

				try {
					log.info("游戏类型：【{}】 正在维护。。。10秒后检查维护时间是否已过。。。", roomDetail.getBetType());
					TimeUnit.SECONDS.sleep(10L);
				} catch (InterruptedException e) {
					log.error(e.getMessage(), e);
				}
			}
		//设置手动干预一次，避免每次都是手动干预
        roomDetail.setHandLottery(false);
	}

	/**
	 * 开奖操作
	 * @param expect
	 * @param type
	 * @param currentIssue
	 * @param roomList
	 */
	private void lotteryOpen(String expect, Integer type, String currentIssue,List<GameRoom> roomList) {
		LotteryDrawRunner runner = new LotteryDrawRunner(gamblingClosuresExecutor,
				betService,
				expect,
				String.valueOf(type),
				currentIssue, roomList,websocketMessageService);
		runner.start();
	}

	/**
	 * 发送游戏期次生成消息
	 * @param expect
	 * @param roomList
	 */
	private void sendExpectInitMessage(String expect, List<GameRoom> roomList) {
		Map<String, String> msgMap = new HashMap<>();
		msgMap.put("expect", expect);
		msgMap.put("status", "0");
		msgMap.put("content", "已生成期号，准备下注");

		gamblingClosuresExecutor.initParams(roomList, JSON.toJSONString(msgMap), websocketMessageService, GameConstants.GAME_STATUS_START);
		gamblingClosuresExecutor.execute();
	}

	/**
	 * 给房间的所有人发送期次封盘消息
	 * @param expect
	 * @param roomList
	 */
	private void sendEntertainedMessage(String expect, List<GameRoom> roomList) {
		// 封盘
		Map<String, String> msgMap = new HashMap<>();
		msgMap.put("expect", expect);
		msgMap.put("status", "1");
		msgMap.put("content", "封盘");
		gamblingClosuresExecutor.initParams(roomList, JSON.toJSONString(msgMap), websocketMessageService, GameConstants.GAME_STATUS_CLOSURE);
		gamblingClosuresExecutor.execute();
	}



	private RoomDetail initRoomDetail(RoomCoreParam roomCoreParam) {
		RoomDetail roomDetail = new RoomDetail();
		Integer initRemainTime = roomCoreParam.getInitRemainTime();
		Integer initExpect = roomCoreParam.getInitExpect();

		// 设置参数
		// 如果是手动进行
		if (roomCoreParam.getManual()) {
			GameRoom roomType = roomCoreParam.getGameRoom();
			roomDetail.setBetType(roomCoreParam.getRelationTO().getGameTypePlayingCode());
			roomDetail.setLotteryName(roomCoreParam.getRelationTO().getGameTypeName());
			roomDetail.setGameTime(roomType.getGameTime());
			roomDetail.setDuration(roomType.getDuration());
			roomDetail.setBetService(betService);
			roomDetail.setHandLottery(true);
			GameRoom roomTime = betService.queryDownTimeByType(roomType.getRoomLotteryTicketType());
			//用户手动输入005期 210秒，则210秒后005期封盘   这里设置期号为004期    开奖时间为004期理论的开奖时间即 当前之间+210秒+提前封盘时间-游戏时间
			// 这里设置为用户设置的初始期的前一期，因为下面会加回来 获取下一期的。
			roomDetail.setExpect(BetUtils.getEarlyExpect(String.valueOf(initExpect)));
			//int changeSecond = initRemainTime + roomType.getDuration();
			int changeSecond = initRemainTime + roomType.getGameTime();
			
			//开奖时间为004期理论的开奖时间即 当前之间+210秒+提前封盘时间-游戏时间
			Calendar calendar = Calendar.getInstance();
			calendar.setTime(new Date());
			calendar.add(Calendar.SECOND, changeSecond);
			roomDetail.setOpenTime(calendar.getTime());
			roomDetail.setLotteryName(roomType.getName());
			if(roomCoreParam.getRelationTO() != null) {
				GameTypePlayingRelationTO gameTypePlayingRelationTO = roomCoreParam.getRelationTO();
				gameTypePlayingRelationTO.setGameTypePlayingTicket(Integer.valueOf(roomType.getRoomLotteryTicketType()));
				roomDetail.setRelationTO(gameTypePlayingRelationTO);
			}
		} else {
			Integer duration = roomCoreParam.getDuration();
			Integer gameTime = roomCoreParam.getGameTime();
			// 初始 服务启动时如果有未开奖的记录 则更新为异常记录
			betService.updateGameIssueError();
			// 获取当前类型房间的初始期号
			roomDetail = getInitExpect(roomCoreParam);
			// 缓存上期开奖时间，如果下面的没获取到需要使用缓存的进行判断
			roomDetail.setGameTime(gameTime);
			roomDetail.setDuration(duration);
			roomDetail.setLotteryName(roomDetail.getRelationTO().getGameTypePlayingName());
			roomDetail.setBetType(roomDetail.getRelationTO().getGameTypePlayingCode());
		}
		return roomDetail;
	}


	/**
	 * 是否停机维护
	 *
	 * @return
	 */
	public RoomDetail isDownTime(GameRoom roomTime, RoomDetail detail) {
		if (roomTime == null) {
			detail.setDownTime(60);
			detail.setDown(true);
		} else {
			Date now = new Date();
			SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
			sdf.setTimeZone(TimeZone.getTimeZone("GMT+8"));
			String nowStr = sdf.format(now);
			try {
				int isDown = BetUtils.isShutDown(nowStr, roomTime.getMaintenance_start_time(),
						roomTime.getMaintenance_end_time());
				if (isDown != -1) {
					detail.setDownTime(isDown);
					detail.setDown(true);
				} else {
					detail.setDownTime(0);
					detail.setDown(false);
				}
			} catch (ParseException e) {
				e.printStackTrace();
			}
		}
		return detail;
	}


	/**
	 * Description: 获取初始游戏期号
	 *
	 * @param param
	 * @return
	 */
	private RoomDetail getInitExpect(RoomCoreParam param) {
		RoomDetail detail = new RoomDetail();
		GameTypePlayingRelationService client = SpringApplicationUtils.getBean(GameTypePlayingRelationService.class);

		GameTypePlayingRelationTO relationTO = client.findByTicket(Integer.parseInt(param.getRoomType()));

		//修改获取游戏时间配置
		detail.setRelationTO(relationTO);
		detail.setDuration(Math.toIntExact(relationTO.getDurationTime()));
		detail.setGameTime(Math.toIntExact(relationTO.getClosedTime()));
		// 默认设置认为API已经down了， 除非下面获取到下期期号和时间
		detail.setApiDown(true);

		ResultEntity re = null;

		// 循环尝试2次 获取接口期号
		RoomManager.RoomBean bean = new RoomManager.RoomBean();
		bean.setBetType(param.getRoomType());
		bean.setBetService(betService);
		int maxTryTimes = 2;
		int tryCount = 0;

		while (tryCount < maxTryTimes) {
			tryCount++;
			re = DataUtil.getIssueApi(bean);
			if (!ResultCode.SUCCESS.equals(re.getStatusCode())) {
				log.error("游戏类型：{}，获取异常：{}", re.getResultInfo(), bean.getBetType());
				try {
					TimeUnit.MILLISECONDS.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			} else {
				break;
			}
		}

		Object retObj = re.getResultInfo();
		if (retObj instanceof Map) {
			@SuppressWarnings("unchecked")
			Map<String, Object> map = (Map<String, Object>) retObj;
			// 获取期号
			detail.setExpect((String) map.get("expect"));
			detail.setOpenTime((Date) map.get("openTime"));
			detail.setApiDown(false);
		}

		log.info("API状态：{}，expect: {}, openTime：{}", !detail.isApiDown(), detail.getExpect(), DateUtils.getDateString(detail.getOpenTime()));
		return detail;
	}

	/**
	 * 获取初始游戏期号
	 *
	 * @param rd
	 * @return
	 * @throws InterruptedException
	 */
	private RoomDetail getInitExpect(RoomDetail rd) {
		// 默认设置认为API已经down了， 除非下面获取到下期期号和时间
		RoomDetail roomDetail = new RoomDetail();
		roomDetail.setBetType(rd.getBetType());
		roomDetail.setGameTime(rd.getGameTime());
		roomDetail.setDuration(rd.getDuration());
		roomDetail.setApiDown(true);

		ResultEntity re = null;
		// 循环尝试2次 获取接口期号
		for (int i = 0; i < 2; i++) {
			re = roomUtils.getIssueApi(roomDetail.getBetType(), roomDetail.getExpect(), roomDetail.getDuration() + roomDetail.getGameTime());
			log.info("获得初始化房间类型【{}】", JSON.toJSONString(re));
			if (!ResultCode.SUCCESS.equals(re.getStatusCode())) {
				log.error("游戏类型:【{}】,获取异常:【{}】", roomDetail.getBetType(), re.getResultInfo());
				try {
					TimeUnit.MICROSECONDS.sleep(500L);
				} catch (InterruptedException e) {
					log.error(e.getMessage(), e);
				}
			} else {
				break;
			}
		}

		Object retObj = re.getResultInfo();
		if (retObj instanceof Map) {
			@SuppressWarnings("unchecked")
			Map<String, Object> map = (Map<String, Object>) retObj;
			// 获取期号
			roomDetail.setExpect((String) map.get("expect"));
			roomDetail.setOpenTime((Date) map.get("openTime"));
			roomDetail.setApiDown(false);
		}
		log.info("API状态是否无效：【{}】, 期号: 【{}】, 开奖时间：【{}】", roomDetail.isApiDown(), roomDetail.getExpect(), DateUtils.getDateString(roomDetail.getOpenTime()));
		return roomDetail;
	}

	private GameTypePlayingRelationTO findByTicketType(List<GameTypePlayingRelationTO> relations, Integer ticketType) {
		for (GameTypePlayingRelationTO relationTO : relations) {
			if (relationTO.getGameTypePlayingTicket().equals(ticketType)) {
				return relationTO;
			}
		}
		return null;
	}

}
